/** @file    BuildingManagerBundle.cpp
 *  @author  Alessandro Polo
 *  @version $Id: BuildingManagerBundle.cpp 2873 2010-08-08 19:06:41Z alex $
 *  @brief
 * File containing methods for the wosh::services::BuildingManagerBundle class.
 * The header for this class can be found in BuildingManagerBundle.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include "BuildingManagerBundle.h"

 #include <core/ObjectFactory.h>
 #include <core/MethodsCommon.h>
 #include <core/Fact.h>
 #include <core/MessageProcessingCommon.h>
 #include <core/List.h>
 #include <core/Table.h>
 #include <core/MessageProcessingCommon.h>
 #include <core/MessageFilter.h>
 #include <core/BusManager.h>
 #include <core/DeviceManager.h>
 #include <core/SystemError.h>
 #include <core/WoshKernel.h>
 #include <core/BusRing.h>
 #include <core/DataModels.h>


using namespace std;
namespace wosh {
 namespace services {

 WOSH_REGISTER(wosh::services::BuildingManagerBundle, "wosh::interfaces::services::BuildingManager", _BuildingManager_VERSION, _static_BuildingManagerBundle1 )
 WOSH_REGISTER(wosh::services::BuildingManagerBundle, "wosh::BundleGeneric", _BuildingManager_VERSION, _static_BuildingManagerBundle2 )

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

BuildingManagerBundle::BuildingManagerBundle()
	: BundleGeneric() {
	BundleGeneric::setName( _BuildingManager_NAME, false );
	Object::getURI().setKernelLocal();
	Object::getURI().setName( "Building" );
	Object::getURI().registerGlobal(); // so BundleGeneric won't overwrite my uri
	Properties.update( _KEY_Version, _BuildingManager_VERSION );
	Requirements.add<wosh::BusRing>();
//Log.setLevel(LOG_VERBOSE);

	Log(LOG_DEBUG, " Setting default properties and permissions.." );
	Properties.set( _BuildingManager_KEY_count,		0, Permission(Permission::Read) );
	Properties.set( _BuildingManager_KEY_DB,		"$DATABASE/building", Permission(Permission::Read) );
	Properties.set( _BuildingManager_KEY_DB_shadow,	"$DATABASE/building/building.shadow", Permission(Permission::Read) );

	Log(LOG_DEBUG, " Registering methods.." );
	MethodWRESULT* mmLoadBuildingObjects = Methods.create<MethodWRESULT>( _BuildingManager_METHOD_load, "Load BuildingObjects from DB", Permission::R_XR__R__() );
	mmLoadBuildingObjects->setMethod( this, (MethodWRESULTPtr)&BuildingManagerBundle::loadBuildingObjects );
	MethodWRESULT* mmSaveBuildingObjects = Methods.create<MethodWRESULT>( _BuildingManager_METHOD_save, "Save BuildingObjects to DB", Permission::R_XR__R__() );
	mmSaveBuildingObjects->setMethod( this, (MethodWRESULTPtr)&BuildingManagerBundle::saveBuildingObjects );

	MethodRequest* mmReloadBObj = Methods.create<MethodRequest>( _BuildingManager_METHOD_reload, "Reload BuildingObjects", Permission::R_XR__R__() );
	mmReloadBObj->setMethod( this, (MethodRequestPtr)&BuildingManagerBundle::mmDoReloadBObj );

	MethodRequest* mmFakeBuildingObjEvent = Methods.create<MethodRequest>( _BuildingManager_METHOD_fake_event, "fake building-object event", Permission::R_XR__R__() );
	mmFakeBuildingObjEvent->setMethod( this, (MethodRequestPtr)&BuildingManagerBundle::mmDoFakeBuildingObjEvent );

	MethodMessageResponse* mmGetPropertiesOf = Methods.create<MethodMessageResponse>( _BuildingManager_METHOD_getchildprop, "get properties of children", Permission::R_XR__R__() );
	mmGetPropertiesOf->setMethod( this, (MethodMessageResponsePtr)&BuildingManagerBundle::mmDoGetPropertiesOf );

	MethodList* mmListBObj = Methods.create<MethodList>( _METHOD_List, "List BuildingObjects", Permission::R_XR_XR__() );
	mmListBObj->setDataFieldExtractor( DataFieldExtractor<BuildingObject>::createFor< DataAdapterMapT<std::string, BuildingObject> >(this->objects, "Building Objects") );

	MethodRetrieve* mmRetrieveBObj = Methods.create<MethodRetrieve>( _METHOD_Retrieve, "Retrieve BuildingObjects(s)", Permission::R_XR_XR__() );
	mmRetrieveBObj->setItemExtractor( ObjectExtractor<BuildingObject, const std::string&>::createFor< DataAdapterMapT<string, BuildingObject> >(this->objects, &Device::getName ) );

	MethodRequest* mmTreeBObj = Methods.create<MethodRequest>( _BuildingManager_METHOD_tree, "Get BuildingObjects tree", Permission::R_XR_XR__() );
	mmTreeBObj->setMethod( this, (MethodRequestPtr)&BuildingManagerBundle::mmDoGetTree );

	setBundleState(Bundle::STATE_CREATED, false);
 }

BuildingManagerBundle::~BuildingManagerBundle() {
	Log(LOG_VERBOSE, " Destroying.." );
	if ( isBundleRunning() ) {
		Log(LOG_WARNING, "~BuildingManagerBundle() : Destroying while Bundle is running! Trying to stop.." );
		bundleStop();
	 }
	if ( this->objects.count() != 0 ) {
		Log(LOG_VERBOSE, ":~BuildingManagerBundle() : Freeing %d BObj..", this->objects.count() );
	 }
	this->objects.clear();
	Log(LOG_VERBOSE, ":~BuildingManagerBundle() : Destroyed." );
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL

WRESULT BuildingManagerBundle::bundleStart() {
	if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STATE_STARTING) )	return WRET_ERR_WRONG_STATE;
	WRESULT bus_ok = WRET_OK;
	if ( !WoshKernel::getInstance()->busses().existsBus(_Bus_Building) ) {
		Log(LOG_INFO, "bundleStart() : Initializing BusRing ("_Bus_Building").." );
		bus_ok = WRET_ERR_INTERNAL;
		Bus* buildingBus = ObjectFactory::createTypeOf<wosh::BusRing>();
		if ( buildingBus == NULL ) {
			SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleStart", "FAILED#%d Creating BusRing instance!" );
		 }
		else {
			buildingBus->setName(_Bus_Building);
			buildingBus->setParentObject(this);
			buildingBus->setSharedLocal(true);//false
			buildingBus->setSharedNetwork(true);
			bus_ok = WoshKernel::getInstance()->busses().registerBus(buildingBus);
			if ( WFAILED(bus_ok) )
				SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleStart", "FAILED#%d Creating Bus ("_Bus_Building")..", bus_ok );
			else {
				Log(LOG_INFO, "bundleStart() :  Created BusRing ("_Bus_Building")." );
			 }
		 }
	 }
	else {
		Log(LOG_VERBOSE, "bundleStart() : "_Bus_Building" exists already." );
	 }
	BusBuilding.setMessageHandler(this);
	bus_ok = BusBuilding.connect( _Bus_Building, Bus::ACTION_DEFERRED );
	if ( WFAILED(bus_ok) )
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleStart", "FAILED#%d Connecting Bus ("_Bus_Building")..", bus_ok );
	else
		Log(LOG_DEBUG, "bundleStart() :  Connected to "_Bus_Building"." );

	WRESULT ret_loaded = loadBuildingObjects();
	if ( WFAILED(ret_loaded) )
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleStart", "FAILED#%d loading objects.", ret_loaded );

	setBundleState( Bundle::STATE_STARTED );
	return ret_loaded + bus_ok;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT BuildingManagerBundle::bundleStop() {
	if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STATE_STOPPING) ) return WRET_ERR_WRONG_STATE;
	WRESULT ret_saved = saveBuildingObjects();
	if ( WFAILED(ret_saved) )
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleStop", "FAILED#%d saving objects.", ret_saved );
	setBundleState( Bundle::STATE_STOPPED );
	return ret_saved;
 }

////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////// BUS EVENTS

void BuildingManagerBundle::busMessage( const Message& message, const Bus* ) {
	if ( message.isEmpty() ) return;
	
	if ( message.getContent()->isFact() ) {
		if ( message.getSource().getName() != "Devices" ) return;
		if ( message.getContent()->isKindOf<DeviceManager_DeviceUpdated>() ) {
			const DeviceManager_DeviceUpdated* devEvent = message.getContent()->as<DeviceManager_DeviceUpdated>();
			if ( devEvent == NULL || !devEvent->hasData() ) return;
			fixReferencesBy( devEvent->getDeviceName() );
		 }
		///@bug BUG handle multiple device-(un)registration
/*
			else if ( fact->getData()->isKindOf<List>() ) {
				const List* dataNames = dynamic_cast<const List*>(fact->getData());
				Log(LOG_INFO, ":busMessage() Calling fixReferencesBy() for %d devices", dataNames->size() );
				for(int i=0; i<dataNames->size(); i++ ) {
					const Variant* vData = dynamic_cast<const Variant*>(dataNames->itemAt(i));
					if ( vData == NULL ) continue;
					fixReferencesBy( vData->toString() );
				 }
			 }
*/
		return;
	 }

	if ( !message.getContent()->isRequest() )
		return;

	// avoid double-response, since there is route core<->building,
	// if source bus is CORE and current is BUILDING, then it's a double message and we drop
	if ( message.getSourceBus() == BusCore.getBusName() && message.getCurrentBus() == BusBuilding.getBusName() )
		return;

	// manage messages for myself
	if ( !MessageFilter::isMessageTarget(message, this) )
		return;

//	BundleGeneric::busMessage(message, source);
	Message* reply = MPC_busMessageRequest( message, Methods, Log );
	if ( reply == NULL )
		return;
	reply->setSource( this );

	if ( message.getCurrentBus() == BusCore.getBusName() )
		BusCore.postMessage(reply);
	else if ( message.getCurrentBus() == BusBuilding.getBusName() )
		BusBuilding.postMessage(reply);

 }

////////////////////////////////////////////////////////////////////////////////////////////////// BUS EVENTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////// METHODS

Response* BuildingManagerBundle::mmDoReloadBObj( const Request* request ) {
	if ( request == NULL ) return NULL;
	std::string option = request->getArgument().toString();
	Log(LOG_VERBOSE, ":mmDoReloadBObj(%s)", option.c_str() );
	this->objects.clear();
	WRESULT ret = loadBuildingObjects();
 	return request->replyResponse(ret);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* BuildingManagerBundle::mmDoTreeBObj( const Request* request ) {
	if ( request == NULL ) return NULL;
	Variant vParentName;
	Variant vTreeMode;
	request->extractArguments(vParentName, vTreeMode);
	Log(LOG_VERBOSE, ":mmDoTreeBObj(%s,%s)", vParentName.toString().c_str(), vTreeMode.toString().c_str() );

	Tree* objTree = new Tree();

	return request->replyResponse(WRET_OK, objTree);
 }

Response* BuildingManagerBundle::mmDoFakeBuildingObjEvent( const Request* request ) {
	if ( request == NULL ) return NULL;
	Variant dDeviceName;
	Variant dEventName;
	Variant dEventData;
	request->extractArguments(dDeviceName, dEventName, dEventData);
	if ( !dDeviceName.isStringNotEmpty() || !dEventName.isStringNotEmpty() )
		return request->replyResponse(WRET_ERR_PARAM, "Invalid arguments [username, location]");
	Log(LOG_VERBOSE, "mmDoFakeBuildingObjEvent(%s) Fact: %s [%s]", dDeviceName.asString().c_str(), dEventName.asString().c_str(), dEventData.toString().c_str() );
	Message* msg_event = new Message();
	msg_event->setSource( this );
	msg_event->getSource().setParent(this->getURI());
	msg_event->getSource().setName(dDeviceName.asString());
	msg_event->setContent( new Fact(dEventName.asString(), new Variant(dEventData)) );
	msg_event->setDestinationBroadcast();
	BusBuilding.postMessage(msg_event);
	return request->replyResponse(WRET_OK, "BuildingObj-Event (message) sent!" );
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* BuildingManagerBundle::mmDoGetPropertiesOf( const Message* requestMessage ) {
	if ( requestMessage == NULL || requestMessage->isEmpty() ) return NULL;
	if ( !requestMessage->getContent()->isRequest() ) return NULL;
	const Request* request = requestMessage->getContent()->asRequest();
	if ( request->getDataClassName() != List::className() ) {
		return request->replyResponse(WRET_ERR_PARAM, "Wrong DataType: not DataList!");
	 }
	const List* datalist_source = dynamic_cast<const List*>( request->getData() );
	if ( datalist_source->size() < 2 )
		return request->replyResponse(WRET_ERR_PARAM, "Wrong DataType: DataList too short (!name)");
	UriExpression bobj_expression;
	bobj_expression.fromString( datalist_source->itemAt(0)->toString() );
	// "type="_Door_TYPE""
	// props name list
	this->objects.transactionBeginRead();
	Table* tbl = new Table( this->objects.size(), 3, true );
	tbl->setTableName("Properties of " + bobj_expression.toString() );
	tbl->setHeaderHorizontal("URI", 0);
	tbl->setHeaderHorizontal("Key", 1);
	tbl->setHeaderHorizontal("Value", 2);
	unsigned int iRow = 0;
	tStrBuildingObjectMap::ConstIterator it;
	const tStrBuildingObjectMap::ConstIterator it_end = this->objects.end();
	for ( it=this->objects.begin(); it!=it_end; ++it ) {
		if ( it->second == NULL ) continue;
		bool valid = MessageFilter::validateExpression(bobj_expression, &it->second->getProperties(), &it->second->getInterfaces(), requestMessage->getSecurityToken());
		if ( !valid ) continue;
		it->second->getProperties().transactionBeginRead();
		for (int i=1;i<datalist_source->size(); i++) {
			PropertyHolder* prop = it->second->getProperties().find(datalist_source->itemAt(i)->toString());
			if ( prop == NULL || prop->data == NULL ) continue;
			///@todo check permissions
			tbl->set( it->second->getURI().clone(), iRow, 0);
			tbl->set<Variant>( prop->data->getKey(), iRow, 1);
			tbl->set( prop->data->getValue().clone(), iRow, 2);
			++iRow;
		 }
		it->second->getProperties().transactionEnd();
	 }
	this->objects.transactionEnd();
	return request->replyResponse(WRET_OK, tbl);
 }

///////////////////////////////////////////////////////////////////////////////////////////////////// METHODS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

 }; // namespace services
}; // namespace wosh
